{{!

  Copyright (c) Facebook, Inc. and its affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{#program:services}}

    #[::async_trait::async_trait]
    pub trait {{service:name}}: ::std::marker::Send + ::std::marker::Sync + 'static {{>lib/block}}{{!
    }}{{#service:requestContext?}}
        type RequestContext: ::std::marker::Sync;{{!
    }}{{/service:requestContext?}}
    {{#service:rustFunctions}}{{^function:returns_streams?}}
        async fn {{function:name}}(
            &self,{{!
            }}{{#service:requestContext?}}
            _request_context: &Self::RequestContext,{{!
            }}{{/service:requestContext?}}{{!
            }}{{#function:args}}
            _{{field:name}}: {{#field:type}}{{>lib/type}}{{/field:type}},{{!
            }}{{/function:args}}
        ) -> ::std::result::Result<{{!
            }}{{#function:returnType}}{{>lib/type}}{{/function:returnType}}, {{!
            }}{{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn{{!
        }}> {
            ::std::result::Result::Err({{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::ApplicationException(
                ::fbthrift::ApplicationException::unimplemented_method(
                    "{{service:name}}",
                    "{{function:name}}",
                ),
            ))
        }
    {{/function:returns_streams?}}{{/service:rustFunctions}}
    }

    #[derive(Clone, Debug)]
    pub struct {{service:name}}Processor<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
        }}{{!
    }}> {
        service: H,{{!
        }}{{#service:extends?}}
        supa: SS,{{!
        }}{{/service:extends?}}{{!
        }}{{^service:extends?}}
        supa: ::fbthrift::NullServiceProcessor<P, R>,{{!
        }}{{/service:extends?}}
        _phantom: ::std::marker::PhantomData<(P, H, R)>,
    }

    impl<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}> {{service:name}}Processor<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}>
    where
        P: ::fbthrift::Protocol + ::std::marker::Send + ::std::marker::Sync + 'static,
        P::Deserializer: ::std::marker::Send,
        H: {{service:name}}{{!
        }}{{#service:requestContext?}}<RequestContext = R>{{/service:requestContext?}},
        R: ::fbthrift::RequestContext<Name = ::const_cstr::ConstCStr> + ::std::marker::Sync,
        <R as ::fbthrift::RequestContext>::ContextStack: ::fbthrift::ContextStack + ::std::marker::Send + ::std::marker::Sync,{{!
        }}{{#service:extends?}}
        SS: ::fbthrift::ThriftService<P::Frame>,
        SS::Handler: {{>lib/super}},
        P::Frame: ::std::marker::Send + 'static,{{!
        }}{{/service:extends?}}
    {
        pub fn new({{!
            }}service: H{{!
            }}{{#service:extends?}}, supa: SS{{/service:extends?}}{{!
        }}) -> Self {
            Self {
                service,
                supa{{^service:extends?}}{{!
                    }}: ::fbthrift::NullServiceProcessor::new(){{!
                }}{{/service:extends?}},
                _phantom: ::std::marker::PhantomData,
            }
        }

        pub fn into_inner(self) -> H {
            self.service
        }{{!
        }}{{#service:rustFunctions}}{{^function:returns_streams?}}

        async fn handle_{{function:name}}<'a>(
            &'a self,
            p: &'a mut P::Deserializer,
            req_ctxt: &R,
            seqid: ::std::primitive::u32,
        ) -> ::anyhow::Result<::fbthrift::ProtocolEncodedFinal<P>> {
            use ::const_cstr::const_cstr;
            use ::fbthrift::ProtocolReader as _;

            const_cstr! {
                SERVICE_NAME = "{{service:name}}";
                METHOD_NAME = "{{service:name}}.{{function:name}}";
            }
            let mut ctx_stack = req_ctxt.get_context_stack(
                &SERVICE_NAME,
                &METHOD_NAME,
            )?;
            ::fbthrift::ContextStack::pre_read(&mut ctx_stack)?;

            static ARGS: &[::fbthrift::Field] = &[
                {{#function:args_by_name}}
                ::fbthrift::Field::new("{{field:name}}", {{#field:type}}{{>lib/ttype}}{{/field:type}}, {{field:key}}),
                {{/function:args_by_name}}
            ];
            {{#function:args}}
            let mut field_{{field:name}} = ::std::option::Option::None;
            {{/function:args}}

            let _ = p.read_struct_begin(|_| ())?;
            loop {
                let (_, fty, fid) = p.read_field_begin(|_| (), ARGS)?;
                match (fty, fid as ::std::primitive::i32) {
                    (::fbthrift::TType::Stop, _) => break,{{!
                    }}{{#function:args}}
                    ({{#field:type}}{{>lib/ttype}}{{/field:type}}, {{field:key}}) => {{!
                        }}field_{{field:name}} = ::std::option::Option::Some({{#field:type}}{{>lib/read}}{{/field:type}}(p)?),{{!
                    }}{{/function:args}}
                    (fty, _) => p.skip(fty)?,
                }
                p.read_field_end()?;
            }
            p.read_struct_end()?;
            ::fbthrift::ContextStack::post_read(&mut ctx_stack, 0)?;
            let res = self.service.{{function:name}}({{!
                }}{{#service:requestContext?}}
                req_ctxt,{{!
                }}{{/service:requestContext?}}{{!
                }}{{#function:args}}
                field_{{field:name}}.ok_or_else(|| {
                    ::fbthrift::ApplicationException::missing_arg(
                        "{{function:name}}",
                        "{{field:name}}",
                    )
                })?,{{!
                }}{{/function:args}}
            ).await;
            let res = match res {
                ::std::result::Result::Ok(res) => {
                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::Success(res)
                }
                ::std::result::Result::Err({{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::ApplicationException(aexn)) => {
                    return ::std::result::Result::Err(aexn.into())
                }
                ::std::result::Result::Err({{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::Success(_)) => {
                    panic!(
                        "{} attempted to return success via error",
                        "{{function:name}}",
                    )
                }
                {{#function:exceptions?}}
                ::std::result::Result::Err(exn) => exn,
                {{/function:exceptions?}}
            };
            ::fbthrift::ContextStack::pre_write(&mut ctx_stack)?;
            let res = ::fbthrift::serialize!(P, |p| ::fbthrift::protocol::write_message(
                p,
                "{{function:name}}",
                ::fbthrift::MessageType::Reply,
                seqid,
                |p| ::fbthrift::Serialize::write(&res, p),
            ));
            ::fbthrift::ContextStack::post_write(&mut ctx_stack, 0)?;
            ::std::result::Result::Ok(res)
        }{{!
        }}{{/function:returns_streams?}}{{/service:rustFunctions}}
    }

    #[::async_trait::async_trait]
    impl<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}> ::fbthrift::ServiceProcessor<P> for {{service:name}}Processor<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}>
    where
        P: ::fbthrift::Protocol + ::std::marker::Send + ::std::marker::Sync + 'static,
        P::Deserializer: ::std::marker::Send,
        H: {{service:name}}{{!
        }}{{#service:requestContext?}}<RequestContext = R>{{/service:requestContext?}},{{!
        }}{{#service:extends?}}
        SS: ::fbthrift::ThriftService<P::Frame>,
        SS::Handler: {{>lib/super}},
        P::Frame: ::std::marker::Send + 'static,{{!
        }}{{/service:extends?}}
        R: ::fbthrift::RequestContext<Name = ::const_cstr::ConstCStr> + ::std::marker::Send + ::std::marker::Sync + 'static,
        <R as ::fbthrift::RequestContext>::ContextStack: ::fbthrift::ContextStack + ::std::marker::Send + ::std::marker::Sync + 'static
    {
        type RequestContext = R;

        #[inline]
        fn method_idx(&self, name: &[::std::primitive::u8]) -> ::std::result::Result<::std::primitive::usize, ::fbthrift::ApplicationException> {
            match name {{>lib/block}}{{!
                }}{{#service:rustFunctions}}{{^function:returns_streams?}}
                b"{{function:name}}" => ::std::result::Result::Ok({{function:index}}usize),{{!
                }}{{/function:returns_streams?}}{{/service:rustFunctions}}
                _ => ::std::result::Result::Err(::fbthrift::ApplicationException::unknown_method()),
            }
        }

        async fn handle_method(
            &self,
            idx: ::std::primitive::usize,
            _p: &mut P::Deserializer,
            _r: &R,
            _seqid: ::std::primitive::u32,
        ) -> ::anyhow::Result<::fbthrift::ProtocolEncodedFinal<P>> {
            match idx {{>lib/block}}{{!
                }}{{#service:rustFunctions}}{{^function:returns_streams?}}
                {{function:index}}usize => self.handle_{{function:name}}(_p, _r, _seqid).await,{{!
                }}{{/function:returns_streams?}}{{/service:rustFunctions}}
                bad => panic!(
                    "{}: unexpected method idx {}",
                    "{{service:name}}Processor",
                    bad
                ),
            }
        }
    }

    #[::async_trait::async_trait]
    impl<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}> ::fbthrift::ThriftService<P::Frame> for {{service:name}}Processor<P, H, R{{!
        }}{{#service:extends?}}, SS{{/service:extends?}}{{!
    }}>
    where
        P: ::fbthrift::Protocol + ::std::marker::Send + ::std::marker::Sync + 'static,
        P::Deserializer: ::std::marker::Send,
        P::Frame: ::std::marker::Send + 'static,
        H: {{service:name}}{{!
        }}{{#service:requestContext?}}<RequestContext = R>{{/service:requestContext?}},{{!
        }}{{#service:extends?}}
        SS: ::fbthrift::ThriftService<P::Frame, RequestContext = R>,
        SS::Handler: {{>lib/super}},
        P::Frame: ::std::marker::Send + 'static,{{!
        }}{{/service:extends?}}
        R: ::fbthrift::RequestContext<Name = ::const_cstr::ConstCStr> + ::std::marker::Send + ::std::marker::Sync + 'static,
        <R as ::fbthrift::RequestContext>::ContextStack: ::fbthrift::ContextStack + ::std::marker::Send + ::std::marker::Sync + 'static
    {
        type Handler = H;
        type RequestContext = R;

        async fn call(
            &self,
            req: ::fbthrift::ProtocolDecoded<P>,
            req_ctxt: &R,
        ) -> ::anyhow::Result<::fbthrift::ProtocolEncodedFinal<P>> {
            use ::fbthrift::{BufExt as _, ProtocolReader as _, ServiceProcessor as _};
            let mut p = P::deserializer(req);
            let (idx, mty, seqid) = p.read_message_begin(|name| self.method_idx(name))?;
            if mty != ::fbthrift::MessageType::Call {
                return ::std::result::Result::Err(::std::convert::From::from(::fbthrift::ApplicationException::new(
                    ::fbthrift::ApplicationExceptionErrorCode::InvalidMessageType,
                    format!("message type {:?} not handled", mty)
                )));
            }
            let idx = match idx {
                ::std::result::Result::Ok(idx) => idx,
                ::std::result::Result::Err(_) => {
                    let cur = P::into_buffer(p).reset();
                    return self.supa.call(cur, req_ctxt).await;
                }
            };
            let res = self.handle_method(idx, &mut p, req_ctxt, seqid).await;
            p.read_message_end()?;
            match res {
                ::std::result::Result::Ok(bytes) => ::std::result::Result::Ok(bytes),
                ::std::result::Result::Err(err) => match err.downcast_ref::<::fbthrift::ProtocolError>() {
                    ::std::option::Option::Some(::fbthrift::ProtocolError::ApplicationException(ae)) => {
                        let res = ::fbthrift::serialize!(P, |p| {
                            ::fbthrift::protocol::write_message(
                                p,
                                "{{service:name}}Processor",
                                ::fbthrift::MessageType::Exception,
                                seqid,
                                |p| ::fbthrift::Serialize::write(&ae, p),
                            )
                        });
                        ::std::result::Result::Ok(res)
                    }
                    _ => ::std::result::Result::Err(err),
                },
            }
        }
    }

    pub fn make_{{service:name}}_server<F, H, R{{!
        }}{{#service:extends?}}, SMAKE, SS{{/service:extends?}}{{!
    }}>(
        proto: ::fbthrift::ProtocolID,
        handler: H,{{!
        }}{{#service:extends?}}
        supa: SMAKE,{{!
        }}{{/service:extends?}}
    ) -> ::std::result::Result<::std::boxed::Box<dyn ::fbthrift::ThriftService<F, Handler = H, RequestContext = R> + ::std::marker::Send + 'static>, ::fbthrift::ApplicationException>
    where
        F: ::fbthrift::Framing + ::std::marker::Send + ::std::marker::Sync + 'static,
        H: {{service:name}}{{!
        }}{{#service:requestContext?}}<RequestContext = R>{{/service:requestContext?}},{{!
        }}{{#service:extends?}}
        SMAKE: ::std::ops::FnOnce(::fbthrift::ProtocolID) -> ::std::result::Result<SS, ::fbthrift::ApplicationException>,
        SS: ::fbthrift::ThriftService<F, RequestContext = R>,
        SS::Handler: {{>lib/super}},{{!
        }}{{/service:extends?}}
        R: ::fbthrift::RequestContext<Name = ::const_cstr::ConstCStr> + ::std::marker::Send + ::std::marker::Sync + 'static,
        <R as ::fbthrift::RequestContext>::ContextStack: ::fbthrift::ContextStack + ::std::marker::Send + ::std::marker::Sync + 'static
    {
        match proto {
            ::fbthrift::ProtocolID::BinaryProtocol => {
                ::std::result::Result::Ok(::std::boxed::Box::new({{service:name}}Processor::<::fbthrift::BinaryProtocol<F>, H, R{{!
                    }}{{#service:extends?}}, SS{{/service:extends?}}{{!
                }}>::new(handler{{!
                    }}{{#service:extends?}}, supa(proto)?{{/service:extends?}}{{!
                }})))
            }
            ::fbthrift::ProtocolID::CompactProtocol => {
                ::std::result::Result::Ok(::std::boxed::Box::new({{service:name}}Processor::<::fbthrift::CompactProtocol<F>, H, R{{!
                    }}{{#service:extends?}}, SS{{/service:extends?}}{{!
                }}>::new(handler{{!
                    }}{{#service:extends?}}, supa(proto)?{{/service:extends?}}{{!
                }})))
            }
            bad => ::std::result::Result::Err(::fbthrift::ApplicationException::invalid_protocol(bad)),
        }
    }{{!
}}{{/program:services}}
{{!newline}}
